import os, sys, io
import gc
import M5
from M5 import *
import network
import m5ui
import lvgl as lv
from unit import RFIDUnit
from hardware import I2C, Pin
import time
from umqtt.simple import MQTTClient
import esp32
from esp32 import NVS
import ujson
import utime
#from audio import Player
from machine import unique_id
import ubinascii
from unit import RGBUnit
import requests
import machine
#player = None
serial = ubinascii.hexlify(unique_id()).decode()

mqtt_last_try = 0
MQTT_RECONNECT_INTERVAL = 5000   # 5 seconds
wifi_last_try = 0
WIFI_RECONNECT_INTERVAL = 5000

# Constants and Configuration


#WIFI_SSID = 'UI-Matrix'
#WIFI_PASS = 'Uimatrix01'
#WIFI_SSID = 'EHTISHAM'
#WIFI_PASS = '123456789'
WIFI_SSID = 'Utopia-WiFi'
WIFI_PASS = '@!nt3lGwn#1'
#WIFI_SSID = 'A16'
#WIFI_PASS = '12345678'
#WIFI_SSID = 'okay.'
#WIFI_PASS = '125125125'
#WIFI_SSID = 'StormFiber-2.4G'
#WIFI_PASS = '22733801'

import requests
import machine

nvs = esp32.NVS("storage")

def get_local_version():
    try:
        return nvs.get_str("fw_version")
    except:
        return "1.0"   # default first install

def set_local_version(version):
    try:
        nvs.set_str("fw_version", version)
        nvs.commit()
        print("Version saved:", version)
    except Exception as e:
        print("NVS save error:", e)

local_version = get_local_version()

def _is_valid_program(code):
    """Reject error pages, truncated downloads and anything that does not compile."""
    if not code or len(code) < 1000:
        return False
    if "def setup" not in code or "def loop" not in code:
        return False
    try:
        compile(code, "main.py", "exec")
    except SyntaxError:
        return False
    except MemoryError:
        print("Not enough memory to verify the update")
        return False
    return True

def install_update(new_code, remote_version):
    """Write the new program to flash, swap it in as main.py, then record the version."""
    with open(OTA_TEMP_PATH, "w") as f:
        f.write(new_code)
    # Keep the current program as a rollback copy (restore it over USB if the new one misbehaves).
    try:
        os.remove(OTA_BACKUP_PATH)
    except OSError:
        pass
    try:
        os.rename(OTA_MAIN_PATH, OTA_BACKUP_PATH)
    except OSError:
        pass  # no main.py yet
    try:
        os.rename(OTA_TEMP_PATH, OTA_MAIN_PATH)
    except OSError:
        # Put the old program back so the device still boots. The old version stays
        # in NVS, so the update is retried on the next boot.
        try:
            os.rename(OTA_BACKUP_PATH, OTA_MAIN_PATH)
        except OSError:
            pass
        raise
    set_local_version(remote_version)

def check_for_update():
    try:
        print("Checking for update...")

        r = requests.get(UPDATE_VERSION_URL)
        try:
            status = getattr(r, "status_code", 200)
            if status != 200:
                print("Version check failed: HTTP", status)
                return
            remote_version = r.text.strip()
        finally:
            r.close()

        print("Remote:", remote_version)
        print("Local :", local_version)

        if float(remote_version) <= float(local_version):
            print("Already latest version")
            return

        print("New version found. Downloading...")
        gc.collect()
        r = requests.get(UPDATE_FILE_URL)
        try:
            status = getattr(r, "status_code", 200)
            if status != 200:
                print("Download failed: HTTP", status)
                return
            new_code = r.text
        finally:
            r.close()

        if not _is_valid_program(new_code):
            print("Update rejected: downloaded file is not a valid program")
            return

        install_update(new_code, remote_version)
        print("Update installed. Restarting...")
        machine.reset()

    except Exception as e:
        print("Update check failed:", e)


UPDATE_VERSION_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/version.txt"
UPDATE_FILE_URL = "https://raw.githubusercontent.com/husbananjum/m5core-s3-UIMETRIX-Z87/refs/heads/main/main.py"
OTA_MAIN_PATH = "/flash/main.py"           # the program the device runs at boot
OTA_TEMP_PATH = "/flash/main_ota_temp.py"  # a download lands here first
OTA_BACKUP_PATH = "/flash/main_prev.py"    # the previous program, kept for manual rollback


MQTT_CLIENT_ID = serial
MQTT_BROKER = '192.168.87.11'
#MQTT_BROKER = 'scada.utopia.pk'
MQTT_PORT = 1883
MQTT_USER = 'utopia'
MQTT_PASSWORD = 'utopia01'
MQTT_RESET_TOPIC = 'reset-topic'
RESET_UID = "D3:58:24:F8:00:00:00:00:00:00"
NVS_NAMESPACE = "rfid_data"
MAX_RETRIES = 5
MQTT_TOPIC = "send_receive_data"
MQTT_LAMP = "lamp_topic"
DEVICE_TYPE = "tag_reader"
DEVICE_NUMBER = serial
MQTT_RESPONSE_DATA = 'response_data'
# UI Elements
ui_elements = {
    'label3': None,  # Last UID display
    'label4': None,  # IP address
    'label5': None,  # Card present status
    'label6': None,  # Current UID
    'title0': None,  # Title
    'label7': None,  # "PROD COUNT:" label
    'label8': None,  # Product counter
    'label1': None,  # "TAG UID:" label
    'line0': None,    # Divider line
    'image1': None,
    'label9': None, # Operator ID
    'label10': None, #MSG
    'label2': None,
    'rect0': None,
    'rect1': None ,
    'rect2': None,
     'rect3':None,
     'rect4':None,
    'image0': None,
    'image1': None,
    'image2': None,
    'image3': None,
    'image4': None,
    'image5': None,
    'image6': None,
    'label12': None,
    'bettery_per': None,
    'charging_icon':None
}
bettery_icon = None
card=0  # card insert=1 card not insert =0
rfid_re_init=0
# System Components
wlan = None
i2c0 = None
rfid_0 = None
rgb_0=None
mqtt_client = None
Operator_ID = "-"
lamp_color = 0 
tag_counter = 0
seen_uids = set()
wifi_retry_count = 0
last_uid_str= "-"
# --- NEW GLOBALS FOR ACK WAIT ---
waiting_for_ack = False
ack_deadline = 0
# --------------------------------
def read_all_fields():
    lengths = [4, 5, 6, 8, 9, 10, 12]
    values = []
    for length in lengths:
        try:
            value = rfid_0.read(length).decode('utf-8').strip('\x00')
        except:
            value = None
        values.append(value)
    return tuple(values)
def get_datetime():
    """Get current datetime in ISO 8601 format (YYYY-MM-DDTHH:MM:SS)"""
    now = utime.localtime()
    return "{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}".format(
        now[0], now[1], now[2], now[3], now[4], now[5])
def safe_label_update(label, text):
    """Safely update label text if label exists"""
    if label is not None:
        label.setText(str(text))
def load_from_nvs():
    global tag_counter, seen_uids, Operator_ID,lamp_color
    
    try:
        nvs = NVS(NVS_NAMESPACE)
        # Load tag counter
        try:
            tag_counter = nvs.get_i32("count")
        except:
            tag_counter = 0
            nvs.set_i32("count", tag_counter)
            nvs.commit()
        
        # Load last UID
        try:
            lamp_color = nvs.get_i32("color")
            print(f" load from nvs {lamp_color}")
        except:
            lamp_color = 0x008000
            nvs.set_i32("color", lamp_color)
            nvs.commit()
            
        # Load seen UIDs
        try:
            uid_count = nvs.get_i32("uid_count")
            seen_uids = set()
            for i in range(uid_count):
                uid_key = f"uid_{i}"
                seen_uids.add(nvs.get_str(uid_key))
        except:
            seen_uids = set()
            nvs.set_i32("uid_count", 0)
            nvs.commit()
            
        # Load Operator ID
        try:
            Operator_ID = nvs.get_str("operator_id")
            if Operator_ID == "" or Operator_ID == "NA":
                Operator_ID = "-"
        except:
            Operator_ID = "-"
            nvs.set_str("operator_id", Operator_ID)
            nvs.commit()
            
        # Update UI with loaded values
        safe_label_update(ui_elements['label8'], str(tag_counter))
        #safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
        safe_label_update(ui_elements['label9'], str(Operator_ID))
        
    except Exception as e:
        print("NVS Load Error:", e)
        tag_counter = 0
        lamp_color = 0x008000
        seen_uids = set()
        Operator_ID = "-"
def save_to_nvs():
    global lamp_color  
    try:
        nvs = NVS(NVS_NAMESPACE)
        nvs.set_i32("count", tag_counter)
        nvs.set_i32("color", lamp_color)
        print(f"save to nvs {lamp_color}")
        nvs.set_str("operator_id", Operator_ID)
        
        # Save seen UIDs
        nvs.set_i32("uid_count", len(seen_uids))
        for i, uid in enumerate(seen_uids):
            nvs.set_str(f"uid_{i}", uid)
            
        nvs.commit()
    except Exception as e:
        print("NVS Save Error:", e)
def publish_rfid_data(sku, color, size, article, remarks, uid, count, cardtype, Operator_ID, serial):
    """Publish RFID data as JSON message with datetime timestamp"""
    global waiting_for_ack
    if waiting_for_ack:
        print("Publish blocked (waiting for ACK)")
        return
    try:
        if mqtt_client:
            message = {
                "card_type": cardtype,
                "operator_id": Operator_ID,
                #"sku": sku,
                #"color": color,
                #"size": size,
                #"article": article,
                #"remarks": remarks,
                "device_type": DEVICE_TYPE,
                #"device_number": DEVICE_NUMBER,
                "uid": uid,
                #"count": count,
                #"timestamp": get_datetime(),
                "device_serial": serial
            }
            mqtt_client.publish(MQTT_TOPIC, ujson.dumps(message), qos=0)
    except Exception as e:
        print("MQTT Publish Error:", e)
        reconnect_mqtt()
def init_ui():
    """Initialize all UI elements with null checks"""
    try:
        Widgets.fillScreen(0xffffff)
        
        #Version info
        
        #employee ID
        ui_elements['rect0'] = Widgets.Rectangle(4, 101, 152, 55, 0x616161, 0xffffff)
        ui_elements['image1'] = Widgets.Image("/flash/res/img/emp.jpg", 5, 102, scale_x=1, scale_y=1)
        #Operator ID
        ui_elements['label9'] = Widgets.Label(str(Operator_ID), 60, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
        #PRoduction Count / Sewing machine
        ui_elements['rect1'] = Widgets.Rectangle(160, 101, 155, 55, 0x616161, 0xffffff)
        ui_elements['image0'] = Widgets.Image("/flash/res/img/mach.jpg", 165, 102, scale_x=1, scale_y=1)
        ui_elements['label8'] = Widgets.Label(str(tag_counter), 220, 124, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
        #RESPONSE DATA MESSAGE
        ui_elements['rect2'] = Widgets.Rectangle(4, 160, 311, 25, 0x616161, 0xffffff)        
        ui_elements['label10'] = Widgets.Label("MSG : ", 8, 166, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
        #RFID PRESENT TAG UID
        ui_elements['rect3'] = Widgets.Rectangle(4, 189, 311, 47, 0x616161, 0xffffff)
        ui_elements['image3'] = Widgets.Image("/flash/res/img/rfid-tag-log.jpg", 5, 190, scale_x=1, scale_y=0.9)
        ui_elements['label6'] = Widgets.Label("-", 60, 205, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
        # Device Serial no
        ui_elements['rect4'] = Widgets.Rectangle(4, 47, 311, 50, 0x616161, 0xffffff)
        ui_elements['image4'] = Widgets.Image("/flash/res/img/bar_c.jpg", 5, 58, scale_x=1.2, scale_y=1.2)
        # WIFI & MQTT LOGO
        ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi.jpg", 149, 3, scale_x=0.9, scale_y=1)
        ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
        ui_elements['label4'] = Widgets.Label("IP: Connecting...", 205, 35, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu9)
        # Utopia Logo
        ui_elements['image6'] = Widgets.Image("/flash/res/img/Logo.jpg", 0, 0)
        
        # Bettery %
        ui_elements['bettery_per'] =Widgets.Label("b%", 230, 15,1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)
        #Version info
        ui_elements['label12'] = Widgets.Label(f"V{local_version}", 275, 55, 1.0, 0x000000, 0xffffff, Widgets.FONTS.DejaVu12)


    except Exception as e:
        print("UI Init Error:", e)
def connect_wifi():
    global wlan, wifi_retry_count
    
    if not wlan.isconnected():
        try:
            safe_label_update(ui_elements['label4'], "WiFi: Connecting...")
            ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
            rgb_0.fill_color(0x0000FF)
            wlan.connect(WIFI_SSID, WIFI_PASS)
            
            # Wait for connection with timeout
            timeout = 10  # 20 seconds timeout
            while not wlan.isconnected() and timeout > 0:
                time.sleep_ms(500)
                timeout -= 0.5
                M5.update()
            
            if wlan.isconnected():
                ip_address = wlan.ifconfig()[0]
                safe_label_update(ui_elements['label4'], "IP:" + ip_address)
                ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi.jpg", 149, 3, scale_x=0.9, scale_y=1)
                rgb_0.fill_color(lamp_color)  
            else:
                safe_label_update(ui_elements['label4'], "WiFi: Timeout")
                ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
                rgb_0.fill_color(0x0000FF)
                wifi_retry_count += 1
                if wifi_retry_count < MAX_RETRIES:
                    time.sleep(2)
                    connect_wifi()
                
        except Exception as e:
            safe_label_update(ui_elements['label4'], "WiFi: Error")
            ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
            print("WiFi Error:", e)
            rgb_0.fill_color(0x0000FF)
            wifi_retry_count += 1
            if wifi_retry_count < MAX_RETRIES:
                time.sleep(2)
                connect_wifi()
def check_wifi():
    global wifi_last_try

    if wlan.isconnected():
        ip = wlan.ifconfig()[0]
        safe_label_update(ui_elements['label4'], "IP:" + ip)
        return

    now = utime.ticks_ms()

    if utime.ticks_diff(now, wifi_last_try) < WIFI_RECONNECT_INTERVAL:
        return

    wifi_last_try = now

    print("WiFi reconnecting...")

    try:
        wlan.disconnect()
    except:
        pass

    wlan.connect(WIFI_SSID, WIFI_PASS)
        
def init_wifi():
    global wlan
    
    try:
        wlan = network.WLAN(network.STA_IF)
        wlan.active(True)
        wlan.config(reconnects=MAX_RETRIES)
        connect_wifi()
    except Exception as e:
        print("WiFi Init Error:", e)
        safe_label_update(ui_elements['label4'], "WiFi Error")
        ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
        rgb_0.fill_color(0x0000FF)
        
def mqtt_callback(*args):
    """Universal callback that handles all MQTT library versions"""
    global tag_counter, seen_uids, last_uid_str, Operator_ID, output, ack, target_serial, data, lamp_color, waiting_for_ack
    
    try:
        # Determine callback signature based on args length
        if len(args) == 2:  # Standard umqtt.simple (topic, message)
            topic, msg = args
        elif len(args) == 4:  # Some variants (topic, msg, retained, duplicate)
            topic, msg, _, _ = args
        else:
            print("Unsupported callback format")
            return
            
        # Ensure proper string decoding
        topic = topic.decode('utf-8') if isinstance(topic, bytes) else topic
        msg = msg.decode('utf-8') if isinstance(msg, bytes) else msg
        
        print(f"MQTT: {topic} -> {msg}")
        
        
        if topic.endswith(MQTT_RESET_TOPIC) and msg.lower() == 'reset':
            # Reset all counters and data
            seen_uids.clear()
            tag_counter = 0
            last_uid_str = "-"
            #Operator_ID = "-"
            
            # Update UI
            safe_label_update(ui_elements['label8'], "0")
            safe_label_update(ui_elements['label3'], "Last UID: - (Reset)")
            #safe_label_update(ui_elements['label9'], "-") #Label for Operator ID
            safe_label_update(ui_elements['label6'], "-")
            safe_label_update(ui_elements['label10'], "MSG : ")
            
            # Persist state
            save_to_nvs()
            print("System reset via MQTT")
        if topic.endswith(MQTT_RESPONSE_DATA):
          try:
            data = ujson.loads(msg)  # decode JSON payload
            target_serial = data.get("device_serial")
            ack = data.get("ack")
            output=data.get("output")
           # Only act if the message is for THIS device
            print(f"target sr= {target_serial}")
            if target_serial == serial and ack == 1:
              # ACK received for this device
              waiting_for_ack = False
              print(output)
              tag_counter += 1
              safe_label_update(ui_elements['label8'], tag_counter)
              safe_label_update(ui_elements['label10'], f"MSG : {output}")
              save_to_nvs()
            elif target_serial == serial and ack == 0:
              # Negative ACK but still targeted to this device
              waiting_for_ack = False
              safe_label_update(ui_elements['label10'], f"MSG : {output}")
              print(output)
            else:
              print("Message ignored, condition does not match")
              #safe_label_update(ui_elements['label10'], f"MSG : ")
          except Exception as e:
            print("response data error:", e)
        if topic.endswith(MQTT_LAMP):
          try:
              data = ujson.loads(msg)  # decode JSON payload
              target_serial = data.get("serial")
              color = data.get("color")
              # Only act if the message is for THIS device
              if target_serial == serial:
                  # Clear waiting flag if message is specifically for this device
                  waiting_for_ack = False
                  if color == "red":
                      print("Set light RED")
                      rgb_0.fill_color(0xff0000)
                      lamp_color = 0xff0000
                      save_to_nvs()
                  elif color == "green":
                      print("Set light GREEN")
                      rgb_0.fill_color(0x008000)
                      lamp_color = 0x008000
                      save_to_nvs()
                  elif color == "yellow":
                      print("Set light yellow")
                      rgb_0.fill_color(0xffff00)
                      lamp_color = 0xffff00
                      save_to_nvs()
                  elif color == "purple":
                      print("Set light purple")
                      rgb_0.fill_color(0x800080)
                      lamp_color = 0x800080
                      save_to_nvs()
                  elif color == "blue":
                      print("Set light blue")
                      rgb_0.fill_color(0x0000FF)
                      lamp_color = 0x0000FF
                      save_to_nvs()
                  else:
                      pass
              else:
                  print("Message ignored, serial does not match")
          except Exception as e:
              print("QC-status topic parse error:", e)
    except Exception as e:
        print("MQTT Callback Error:", e)

def init_mqtt():
    global mqtt_client

    try:
        if not wlan.isconnected():
            print("WiFi not connected")
            return

        mqtt_client = MQTTClient(
            client_id=MQTT_CLIENT_ID,
            server=MQTT_BROKER,
            port=MQTT_PORT,
            user=MQTT_USER,
            password=MQTT_PASSWORD,
            keepalive=60,
            ssl=False
        )

        mqtt_client.set_callback(mqtt_callback)
        mqtt_client.connect()

        mqtt_client.subscribe(MQTT_RESET_TOPIC)
        mqtt_client.subscribe(MQTT_RESPONSE_DATA)
        mqtt_client.subscribe(MQTT_LAMP)

        print("MQTT Connected")

        ui_elements['image5'] = Widgets.Image("/flash/res/img/MQTT_Cloud.jpg",100,0)

        rgb_0.fill_color(lamp_color)

    except Exception as e:
        print("MQTT init error:", e)
        mqtt_client = None
        ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)

def reconnect_mqtt():
    global mqtt_client, mqtt_last_try

    now = utime.ticks_ms()

    # prevent continuous reconnect spam
    if utime.ticks_diff(now, mqtt_last_try) < MQTT_RECONNECT_INTERVAL:
        return

    mqtt_last_try = now

    print("MQTT reconnect attempt")

    try:
        if mqtt_client:
            try:
                mqtt_client.disconnect()
            except:
                pass

        mqtt_client = None

        if not wlan.isconnected():
            print("WiFi not connected, skipping MQTT reconnect")
            ui_elements['image2'] = Widgets.Image("/flash/res/img/wifi_error.jpg", 150, 3, scale_x=1.1, scale_y=1)
            return

        init_mqtt()

    except Exception as e:
        print("MQTT reconnect error:", e)
        ui_elements['image5'] = Widgets.Image("/flash/res/img/mqtt_error.jpg", 104, 1, scale_x=1, scale_y=1)
        mqtt_client = None
def loop():
    global tag_counter, seen_uids, last_uid_str, mqtt_client, serial, player, Operator_ID ,battery_per , bettery_icon, card,rfid_re_init,i2c0,rfid_0,lamp_color, waiting_for_ack, ack_deadline
    
    #M5.update()
    
    serial = ubinascii.hexlify(unique_id()).decode()
    check_wifi()
    time.sleep(0.1)
    bettery_icon.set_value(Power.getBatteryLevel(), True)
    #bettery_per.set_text(f"{str(Power.getBatteryLevel())}%")
    safe_label_update(ui_elements['bettery_per'], str(Power.getBatteryLevel()))
    if Power.isCharging():
      ui_elements['charging_icon'] = Widgets.Image("/flash/res/img/charging.jpg", 305, 11)
    else:
      ui_elements['charging_icon'] = Widgets.Image("/flash/res/img/not_charging.jpg", 305,11)
    
    if not wlan.isconnected():
        connect_wifi()
        if wlan.isconnected():
          init_mqtt()
          print("MQTT RE-INITIALIZE SUCCESS")
        if not wlan.isconnected():
            time.sleep(1)
            return
    
    # Check for MQTT messages
    try:
      if mqtt_client:
        mqtt_client.check_msg()
    except Exception as e:
        print("MQTT error:", e)
        mqtt_client = None
    
    # --- ACK TIMEOUT HANDLING (non-blocking) ---
    if waiting_for_ack:
        # utime.ticks_diff returns positive when now >= ack_deadline
        if utime.ticks_diff(utime.ticks_ms(), ack_deadline) >= 0:
            print("ACK TIMEOUT: No response received")
            safe_label_update(ui_elements['label10'], "MSG : NO RESPONSE RECIEVED")
            waiting_for_ack = False
    # ------------------------------------------------
    # Ensure MQTT connection
    if wlan.isconnected() and mqtt_client is None:
        reconnect_mqtt()
    # RFID Processing
    try:
        if rfid_re_init==1:
          i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
          rfid_0 = RFIDUnit(i2c0)
          rfid_re_init=0
          print("RFID RE_INITIALIZED")
        else:
          pass
        card_present = rfid_0.is_new_card_present() if rfid_0 else False
        safe_label_update(ui_elements['label5'], card_present)
        if waiting_for_ack:
            # Show card is ignored during waiting
            if card_present:
                safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
            return
        
        if card_present:
            current_uid = rfid_0.read_card_uid() if rfid_0 else None
            if current_uid:
                
                # Convert UID bytearray to consistent string format
                uid_str = ':'.join(['%02X' % b for b in current_uid])
                safe_label_update(ui_elements['label6'], uid_str)
                # Read all fields from the card
                sku, color, size, article, remarks, cardtype, new_operator_id = read_all_fields()
                rfid_0.close()
                # Update Operator ID if we have a new valid ID
                if new_operator_id and new_operator_id != '0' and cardtype=='Operator' and card==0:
                    card=1
                    Operator_ID = new_operator_id
                    safe_label_update(ui_elements['label9'], str(Operator_ID))
                    # Save the new Operator ID immediately
                    save_to_nvs()
                    publish_rfid_data(sku, color, size, article, remarks, last_uid_str, tag_counter, cardtype, Operator_ID, serial)
                    # --- START WAIT FOR ACK (seconds) ---
                    safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
                    waiting_for_ack = True
                    ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
                    # ---------------------------------------
                    #player.play_tone(2000, 0.04, volume=100, sync=True)
                    #player.play("file://flash/res/audio/beep.mp3", pos=0, volume=100, sync=True)
                #if uid_str == RESET_UID:
                if cardtype == "Reset":
                    # Reset logic
                    seen_uids.clear()
                    tag_counter = 0
                    last_uid_str = "-"
                    Operator_ID = "0"
                    safe_label_update(ui_elements['label8'], "0")
                    safe_label_update(ui_elements['label3'], "Last UID: - (Reset)")
                    safe_label_update(ui_elements['label9'], "-")
                    safe_label_update(ui_elements['label10'], "MSG : ")
                    save_to_nvs()
                    publish_rfid_data("RESET", "RESET", "RESET", "RESET", "RESET", "RESET", "RESET", "RESET", 0, serial)
                    # --- START WAIT FOR ACK (seconds) AFTER RESET ---
                    safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
                    waiting_for_ack = True
                    ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
                    # ---------------------------------------
                    return
                
                # Update last UID shown
                last_uid_str = uid_str
                safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
                
                #if uid_str not in seen_uids and cardtype == 'Product':
                if cardtype == 'Product' and card==0:
                    card=1  
                    #seen_uids.add(uid_str)
                    #if cardtype == 'Product':
                     #   tag_counter += 1
                    safe_label_update(ui_elements['label8'], str(tag_counter))
                    save_to_nvs()
                    publish_rfid_data(sku, color, size, article, remarks, last_uid_str, tag_counter, cardtype, Operator_ID, serial)
                    # --- START WAIT FOR ACK (seconds) ---
                    safe_label_update(ui_elements['label10'], "MSG : WAITING FOR RESPONSE")
                    waiting_for_ack = True
                    ack_deadline = utime.ticks_add(utime.ticks_ms(), 10000)
                    # ---------------------------------------
                    #player.play_tone(2000, 0.04, volume=100, sync=True)
                    #player.play("file://flash/res/audio/beep.mp3", pos=0, volume=100, sync=True)
        else:
            safe_label_update(ui_elements['label6'], "-")
            safe_label_update(ui_elements['label3'], f"Last UID: {last_uid_str}")
            card=0
        
        #mqtt_client.publish(f"M5devices/{DEVICE_NUMBER}", ujson.dumps({"online":"True","operator_id":f"{Operator_ID}","serial":f"{serial}"}), qos=0)
        if mqtt_client:
            mqtt_client.publish(f"M5devices/{DEVICE_NUMBER}", ujson.dumps({}), qos=0)
        
    except Exception as e:
        print("RFID Processing Error:", e)
        safe_label_update(ui_elements['label6'], "RFID Error")
        rfid_re_init=1
    
    M5.update()
    time.sleep(0.1)
def setup():
    global wlan, i2c0, rfid_0, mqtt_client, label2, player , battery_per , bettery_icon , rfid_re_init,rgb_0,lamp_color
    #machine.WDT(timeout=60000)
    M5.begin()
    m5ui.init()
    bettery_icon = m5ui.M5Bar(x=256, y=7, w=49, h=25, min_value=0, max_value=100, value=25, bg_c=0x616161, color=0x21f398)
    #player = Player(None)
    rgb_0 = RGBUnit((8, 9), 10)
    
    load_from_nvs()
    print(lamp_color)
    #if lamp_color==16776960:
      #rgb_0.fill_color(0xffff00)
    
    #rgb_0.fill_color(lamp_color)  
    rgb_0.fill_color(0x0000FF)
    init_ui()
    label2 = Widgets.Label("SR:  LOADING...", 70, 70, 1.0,0x000000, 0xffffff, Widgets.FONTS.DejaVu18)
    init_wifi()
    if wlan.isconnected():
      check_for_update() #check update files from git hub

    serial = ubinascii.hexlify(unique_id()).decode()
    
    label2.setText("SR: " + serial)
    label2.setColor(0x000000, 0xffffff)
    
    # Initialize RFID reader
    try:
        i2c0 = I2C(0, scl=Pin(1), sda=Pin(2), freq=100000)
        rfid_0 = RFIDUnit(i2c0)
        rfid_re_init=0
    except Exception as e:
        print("RFID Init Error:", e)
        safe_label_update(ui_elements['label6'], "RFID Error")
        rfid_re_init=1
    
    # Initialize MQTT
    init_mqtt()
    #rgb_0.fill_color(0x33ff33)
if __name__ == '__main__':
    try:
        setup()
        while True:
            loop()
    except Exception as e:
        print("Main Error:", e)
        try:
            from utility import print_error_msg
            print_error_msg(e)
        except ImportError:
            print("Please update firmware")